<?php

/**
 * Simple Persistent data class.
 * To use, set the database up with columns the same as your class' property names,  
 * and make your class extend this class.
 * The following column types are supported:
 * TEXT, VARCHAR, INT, FLOAT
 * Use UNIX timestamps for dates.
 * Now whenever you want, you can use the class' static find() method to
 * get an array of objects.  
 * Example class:
 * <?php
 * require_once('simplepdo.php');
 * class Details extends SimplePDO {
 *     public $address;
 *     public $phone;
 *     public $email;
 *     public $age;
 *     public $height;
 * }
 * ?>
 * Example usage:
 * <?php
 * define('SPDO_DB_NAME', 'databasename');
 * define('SPDO_DB_HOST', 'localhost');
 * define('SPDO_DB_USER', 'username');
 * define('SPDO_DB_PASS', 'password');
 * require_once('Details.php'); // the file above
 * // Create a new Details persistent object
 * $details = Details::create();
 * $oid = $details->oid;
 * $details->address = "303 Teebee Street,\nRolandsville";
 * $details->age = 87;
 * $details->commit();  // Saves it to the database.
 * unset($details);
 * // Retrieve that specific Details object back from the database
 * $objects = Details::find('spdo_oid = '.$oid); // use the actual column name
 * if ( count($objects) == 0 ) {
 *     die("Didn't find it");
 * }
 * $details = array_shift($objects);
 * echo '<p>Address was '.$details->address.'</p>';
 * // Print out all the Details objects
 * $objects = Details::find();
 * foreach( $objects as $details ) {
 *     echo '<p>'.var_export($details,true).'</p>';
 * }
 * ?>
 */

/* Database connection details */
/*
define('SPDO_DB_NAME', 'name of database');
define('SPDO_DB_HOST', 'localhost');
define('SPDO_DB_USER', 'username');
define('SPDO_DB_PASS', 'password');
*/

/* Database settings */
if ( ! defined('SPDO_DB_PREFIX') ) {
    define('SPDO_DB_PREFIX', '');
}
if ( ! defined('SPDO_OID_COLUMN') ) {
    define('SPDO_OID_COLUMN', 'spdo_oid');
}

require_once(dirname(__FILE__).'/functions.php');

class SimplePDO {

    /* Holds the classname for each extended class - necessary due to lack of
     * late static bindings in PHP <5.2 */
    protected static $spdo_class;

    /* Stores information about what type each variable in each class is. */
    public static $spdo_types;

    /* The object ID used as the database primary key */
    public $oid;

    /**
     * Returns an array of objects matching the options given.
     * @param $options (String) The WHERE clause of the SQL query to run. Eg.
     *          "`foo` = 'bar' OR `bar` = 7"
     * @return (Array) An array of objects.  If there are no results, returns
     *          an empty array.
     */
    static function find($options='') {
        $class = self::spdo_get_called_class();
        if ( $options == '' ) {
            $options = '1 = 1';
        }
        $objects = array();
        // Get records from database that match options
        simplepdo::spdo_db_connect();
        $sql = sprintf(
            'SELECT * FROM `%s%s` WHERE %s',
            SPDO_DB_PREFIX,
            $class,
            $options
        );
        $records = mysql_query($sql);
        // Assemble into objects
        while ( $record = mysql_fetch_assoc($records) ) {
            $item = new $class();
            foreach( $record as $key => $value ) {
                if ($key == SPDO_OID_COLUMN) $key = 'oid';
                $item->$key = $value;
            }
            $objects[] = $item;
        }
        return $objects;
    }
    
    /**
     * Creates and returns a new object.  The object will have an entry in the 
     * database, and will have an oid set, but no other values will be set.
     * @return (Object) An object.  If something goes wrong, returns false.
     */
    static function create() {
        $class = self::spdo_get_called_class();
        simplepdo::spdo_db_connect();
        $sql = sprintf(
            "INSERT INTO `%s%s` SET `%s` = NULL",
            SPDO_DB_PREFIX,
            $class,
            SPDO_OID_COLUMN
        );
        mysql_query($sql);
        $oid = mysql_insert_id();
        $objects = self::find(SPDO_OID_COLUMN." = ".$oid);
        if ( count($objects) > 0 ) {
            return $objects[0];
        }
        return false;
    }
    
    /**
     * Deletes the object from the database.
     */
    function delete() {
        // Do the database delete
        simplepdo::spdo_db_connect();
        $sql = sprintf(
            "DELETE FROM `%s%s` WHERE `%s` = %s",
            SPDO_DB_PREFIX,
            get_class($this),
            SPDO_OID_COLUMN,
            $this->oid
        );
        mysql_query($sql);
        // Clean up this object
        foreach( get_object_vars($this) as $var => $value ) {
            $this->$var = null;
        }
    }

    /**
     * Saves any changes to the object into the database.
     */
    function commit() {
        $class = get_class($this);
        $set = '';
        foreach( simplepdo::spdo_get_types($class) as $var => $value ) {
            // Figure out whether to quote value in SQL
            switch ( $type ) {
                case 'varchar':
                case 'text':
                default:
                    $set .= sprintf(
                        " `%s` = '%s', ",
                        $var,
                        $this->$var
                    );
                    break;
                case 'int':
                case 'float':
                    $set .= sprintf(
                        " `%s` = %s, ",
                        $var,
                        $this->$var
                    );
                    break;
            }
        }
        $set = substr($set, 0, -2); // remove trailing comma
        $sql = sprintf(
            "UPDATE `%s%s` SET %s WHERE %s = %s",
            SPDO_DB_PREFIX,
            $class,
            $set,
            SPDO_OID_COLUMN,
            $this->oid
        );
        simplepdo::spdo_db_connect();
        mysql_query($sql);
    }

    /* Automatically save changes when object is destroyed. */

    function __destruct() {
        $this->commit();
    }   

    //////////     I N T E R N A L   F U N C T I O N S

    /** @private
     * Returns the name of the class being called.
     * @return (String) The name of the class that was called.
     */
    function spdo_get_called_class() {
        if ( ! self::$spdo_class ) {
            self::$spdo_class = get_called_class();
        }
        return self::$spdo_class;
    }

    /** @private
     * Extracts the types for each field from the database.
     * @param $class (String) The name of the class to extract types for.  Since we
     *          can't get at the name of the called class inside static methods,
     *          we have to pass it in.
     */
    private static function spdo_get_types($class) {
        if ( ! is_array(simplepdo::$spdo_types) ) {
             simplepdo::$spdo_types = array();
        }
        if ( array_key_exists($class, simplepdo::$spdo_types) ) {
            return simplepdo::$spdo_types[$class];
        }
        simplepdo::spdo_db_connect();
        $result = mysql_query('SHOW COLUMNS FROM `'.$class.'`');
        if (!$result) {
            throw new ErrorException('Database error: ' . mysql_error());
        }
        $types = array();
        if ( mysql_num_rows($result) > 0 ) {
            while ( $row = mysql_fetch_assoc($result) ) {
                if ( $row['Field'] == SPDO_OID_COLUMN ) continue;
                $type = 'text';
                if ( strpos($row['Type'], 'varchar') !== false ) {
                    $type = 'varchar';
                }
                if ( strpos($row['Type'], 'int') !== false ) {
                    $type = 'int';
                }
                if ( strpos($row['Type'], 'float') !== false ) {
                    $type = 'float';
                }
                $types[$row['Field']] = $type;
            }
        }
        simplepdo::$spdo_types[$class] = $types;
        return simplepdo::$spdo_types[$class];
    }
    
    /**
     * Connects to the database if needed.
     */
    private static function spdo_db_connect() {
        $db = mysql_connect(SPDO_DB_HOST, SPDO_DB_USER, SPDO_DB_PASS);
        if ( $db == false ) {
            throw new ErrorException('Database error: '.mysql_error());
        }
        mysql_select_db(SPDO_DB_NAME);
    } 

}

?>
